/*
 * @Author: xiaosihan 
 * @Date: 2023-05-09 00:35:17 
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2025-01-28 04:02:16
 */

import { Camera, Color, DepthFormat, DepthTexture, LinearFilter, NoBlending, NormalBlending, Object3D, RGBAFormat, RawShaderMaterial, Scene, ShaderChunk, ShaderMaterial, UnsignedShortType, Vector2, WebGLRenderTarget, WebGLRenderer } from "three";
import FullScreenQuad from "./fsQuad";
import fsQuad from "./fsQuad";


// 选中对象的深度纹理
const selectDepthTexture = (() => {
    const baseDepthTexture = new DepthTexture(1024, 1024);
    baseDepthTexture.format = DepthFormat;
    baseDepthTexture.type = UnsignedShortType;
    baseDepthTexture.anisotropy = 16;
    return baseDepthTexture;
})();

// 选中对象的纹理
const selectTextureTarget = new WebGLRenderTarget(512, 512, {
    minFilter: LinearFilter,
    magFilter: LinearFilter,
    format: RGBAFormat,
    depthTexture: selectDepthTexture,
    anisotropy: 16,
});

// 深度测试后的选中纹理
const selectDethpTestTextureTarget = new WebGLRenderTarget(512, 512, {
    minFilter: LinearFilter,
    magFilter: LinearFilter,
    format: RGBAFormat,
    anisotropy: 16,
});

// 由于不能同时读写自己的纹理 
const blur1TextureTarget = new WebGLRenderTarget(512, 512, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });

// 所以需要用2个模糊纹理相互读写
const blur2TextureTarget = new WebGLRenderTarget(512, 512, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });

const size = new Vector2();

//深度比对纹理
const depthTestShader = new ShaderMaterial({
    uniforms: {
        map: { value: null }, // 贴图纹理
        baseDepth: { value: null }, // 整个场景的深度纹理
        selectDepth: { value: null }, // 选中的对象深度纹理
    },
    vertexShader: `
        ${ShaderChunk.common}
        ${ShaderChunk.logdepthbuf_pars_vertex}
        varying vec2 vUv;
        void main() {
            vUv = uv;
            // gl_Position = vec4(position, 1.0);
            // gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);
            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            ${ShaderChunk.logdepthbuf_vertex}
        }
    `,
    fragmentShader: `
        ${ShaderChunk.logdepthbuf_pars_fragment}
        uniform sampler2D map;
        uniform sampler2D baseDepth;
        uniform sampler2D selectDepth;
        varying vec2 vUv;
        void main() {
            if(texture2D(baseDepth, vUv).r < texture2D(selectDepth, vUv).r){
                discard;
            }
            if(texture2D(baseDepth, vUv).r == 1.0){
                discard;
            }
            gl_FragColor = texture2D(map, vUv);
            ${ShaderChunk.logdepthbuf_fragment}
        }
    `,
    blending: NoBlending, // 渲染时不与背景融合计算 直接替换
    depthTest: true,
    depthWrite: true,
    transparent: true
});

// 抗锯齿着色器
const FXAAShaderMaterial = new ShaderMaterial({
    uniforms: {
        tDiffuse: { value: null }, // 贴图纹理
        resolution: { value: new Vector2(1 / 1024, 1 / 1024) }
    },
    vertexShader: `
        ${ShaderChunk.common}
        ${ShaderChunk.logdepthbuf_pars_vertex}
        varying vec2 vUv;
        void main() {
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            ${ShaderChunk.logdepthbuf_vertex}
        }
    `,
    fragmentShader: `
        ${ShaderChunk.logdepthbuf_pars_fragment}
        precision highp float;

        uniform sampler2D tDiffuse;
    
        uniform vec2 resolution;
    
        varying vec2 vUv;
    
        #ifndef FXAA_DISCARD
                //
                // Only valid for PC OpenGL currently.
                // Probably will not work when FXAA_GREEN_AS_LUMA = 1.
                //
                // 1 = Use discard on pixels which don't need AA.
                //     For APIs which enable concurrent TEX+ROP from same surface.
                // 0 = Return unchanged color on pixels which don't need AA.
                //
                #define FXAA_DISCARD 0
        #endif
    
        /*--------------------------------------------------------------------------*/
        #define FxaaTexTop(t, p) texture2D(t, p, -100.0)
        #define FxaaTexOff(t, p, o, r) texture2D(t, p + (o * r), -100.0)
        /*--------------------------------------------------------------------------*/
    
        #define NUM_SAMPLES 5
    
        // assumes colors have premultipliedAlpha, so that the calculated color contrast is scaled by alpha
        float contrast( vec4 a, vec4 b ) {
                vec4 diff = abs( a - b );
                return max( max( max( diff.r, diff.g ), diff.b ), diff.a );
        }
    
        /*============================================================================
    
                                        FXAA3 QUALITY - PC
    
        ============================================================================*/
    
        /*--------------------------------------------------------------------------*/
        vec4 FxaaPixelShader(
                vec2 posM,
                sampler2D tex,
                vec2 fxaaQualityRcpFrame,
                float fxaaQualityEdgeThreshold,
                float fxaaQualityinvEdgeThreshold
        ) {
                vec4 rgbaM = FxaaTexTop(tex, posM);
                vec4 rgbaS = FxaaTexOff(tex, posM, vec2( 0.0, 1.0), fxaaQualityRcpFrame.xy);
                vec4 rgbaE = FxaaTexOff(tex, posM, vec2( 1.0, 0.0), fxaaQualityRcpFrame.xy);
                vec4 rgbaN = FxaaTexOff(tex, posM, vec2( 0.0,-1.0), fxaaQualityRcpFrame.xy);
                vec4 rgbaW = FxaaTexOff(tex, posM, vec2(-1.0, 0.0), fxaaQualityRcpFrame.xy);
                // . S .
                // W M E
                // . N .
    
                bool earlyExit = max( max( max(
                        contrast( rgbaM, rgbaN ),
                        contrast( rgbaM, rgbaS ) ),
                        contrast( rgbaM, rgbaE ) ),
                        contrast( rgbaM, rgbaW ) )
                        < fxaaQualityEdgeThreshold;
                // . 0 .
                // 0 0 0
                // . 0 .
    
                #if (FXAA_DISCARD == 1)
                        if(earlyExit) FxaaDiscard;
                #else
                        if(earlyExit) return rgbaM;
                #endif
    
                float contrastN = contrast( rgbaM, rgbaN );
                float contrastS = contrast( rgbaM, rgbaS );
                float contrastE = contrast( rgbaM, rgbaE );
                float contrastW = contrast( rgbaM, rgbaW );
    
                float relativeVContrast = ( contrastN + contrastS ) - ( contrastE + contrastW );
                relativeVContrast *= fxaaQualityinvEdgeThreshold;
    
                bool horzSpan = relativeVContrast > 0.;
                // . 1 .
                // 0 0 0
                // . 1 .
    
                // 45 deg edge detection and corners of objects, aka V/H contrast is too similar
                if( abs( relativeVContrast ) < .3 ) {
                        // locate the edge
                        vec2 dirToEdge;
                        dirToEdge.x = contrastE > contrastW ? 1. : -1.;
                        dirToEdge.y = contrastS > contrastN ? 1. : -1.;
                        // . 2 .      . 1 .
                        // 1 0 2  ~=  0 0 1
                        // . 1 .      . 0 .
    
                        // tap 2 pixels and see which ones are "outside" the edge, to
                        // determine if the edge is vertical or horizontal
    
                        vec4 rgbaAlongH = FxaaTexOff(tex, posM, vec2( dirToEdge.x, -dirToEdge.y ), fxaaQualityRcpFrame.xy);
                        float matchAlongH = contrast( rgbaM, rgbaAlongH );
                        // . 1 .
                        // 0 0 1
                        // . 0 H
    
                        vec4 rgbaAlongV = FxaaTexOff(tex, posM, vec2( -dirToEdge.x, dirToEdge.y ), fxaaQualityRcpFrame.xy);
                        float matchAlongV = contrast( rgbaM, rgbaAlongV );
                        // V 1 .
                        // 0 0 1
                        // . 0 .
    
                        relativeVContrast = matchAlongV - matchAlongH;
                        relativeVContrast *= fxaaQualityinvEdgeThreshold;
    
                        if( abs( relativeVContrast ) < .3 ) { // 45 deg edge
                                // 1 1 .
                                // 0 0 1
                                // . 0 1
    
                                // do a simple blur
                                return mix(
                                        rgbaM,
                                        (rgbaN + rgbaS + rgbaE + rgbaW) * .25,
                                        .4
                                );
                        }
    
                        horzSpan = relativeVContrast > 0.;
                }
    
                if(!horzSpan) rgbaN = rgbaW;
                if(!horzSpan) rgbaS = rgbaE;
                // . 0 .      1
                // 1 0 1  ->  0
                // . 0 .      1
    
                bool pairN = contrast( rgbaM, rgbaN ) > contrast( rgbaM, rgbaS );
                if(!pairN) rgbaN = rgbaS;
    
                vec2 offNP;
                offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x;
                offNP.y = ( horzSpan) ? 0.0 : fxaaQualityRcpFrame.y;
    
                bool doneN = false;
                bool doneP = false;
    
                float nDist = 0.;
                float pDist = 0.;
    
                vec2 posN = posM;
                vec2 posP = posM;
    
                int iterationsUsed = 0;
                int iterationsUsedN = 0;
                int iterationsUsedP = 0;
                for( int i = 0; i < NUM_SAMPLES; i++ ) {
                        iterationsUsed = i;
    
                        float increment = float(i + 1);
    
                        if(!doneN) {
                                nDist += increment;
                                posN = posM + offNP * nDist;
                                vec4 rgbaEndN = FxaaTexTop(tex, posN.xy);
                                doneN = contrast( rgbaEndN, rgbaM ) > contrast( rgbaEndN, rgbaN );
                                iterationsUsedN = i;
                        }
    
                        if(!doneP) {
                                pDist += increment;
                                posP = posM - offNP * pDist;
                                vec4 rgbaEndP = FxaaTexTop(tex, posP.xy);
                                doneP = contrast( rgbaEndP, rgbaM ) > contrast( rgbaEndP, rgbaN );
                                iterationsUsedP = i;
                        }
    
                        if(doneN || doneP) break;
                }
    
    
                if ( !doneP && !doneN ) return rgbaM; // failed to find end of edge
    
                float dist = min(
                        doneN ? float( iterationsUsedN ) / float( NUM_SAMPLES - 1 ) : 1.,
                        doneP ? float( iterationsUsedP ) / float( NUM_SAMPLES - 1 ) : 1.
                );
    
                // hacky way of reduces blurriness of mostly diagonal edges
                // but reduces AA quality
                dist = pow(dist, .5);
    
                dist = 1. - dist;
    
                return mix(
                        rgbaM,
                        rgbaN,
                        dist * .5
                );
        }
    
        void main() {
                const float edgeDetectionQuality = .2;
                const float invEdgeDetectionQuality = 1. / edgeDetectionQuality;
    
                gl_FragColor = FxaaPixelShader(
                        vUv,
                        tDiffuse,
                        resolution,
                        edgeDetectionQuality, // [0,1] contrast needed, otherwise early discard
                        invEdgeDetectionQuality
                );
            ${ShaderChunk.logdepthbuf_fragment}
        }
    `,
    blending: NoBlending, // 渲染时不与背景融合计算 直接替换
    depthTest: false,
    depthWrite: false,
    transparent: true
});

// 描边着色器
const strokeShader = new RawShaderMaterial({
    uniforms: {
        tDiffuse: { value: null },
        color: { value: new Color("#0f0") }, // 没有被挡住的地方的颜色绿色
        width: { value: 1.0 },
        opacity: { value: 1.0 },
        resolution: { value: new Vector2(1 / 512, 1 / 512) }, // 渲染器的分辨率
        offset: {
            value: [
                new Vector2(0.000, 1.000),
                new Vector2(0.259, 0.966),
                new Vector2(0.500, 0.866),
                new Vector2(0.707, 0.707),
                new Vector2(0.866, 0.500),
                new Vector2(0.966, 0.259),
                new Vector2(1.000, 0.000),
                new Vector2(0.966, -0.259),
                new Vector2(0.866, -0.500),
                new Vector2(0.707, -0.707),
                new Vector2(0.500, -0.866),
                new Vector2(0.259, -0.966),
                new Vector2(0.000, -1.000),
                new Vector2(-0.259, -0.966),
                new Vector2(-0.500, -0.866),
                new Vector2(-0.707, -0.707),
                new Vector2(-0.866, -0.500),
                new Vector2(-0.966, -0.259),
                new Vector2(-1.000, -0.000),
                new Vector2(-0.966, 0.259),
                new Vector2(-0.866, 0.500),
                new Vector2(-0.707, 0.707),
                new Vector2(-0.500, 0.866),
                new Vector2(-0.259, 0.966)
            ]
        }
    },
    vertexShader: `
		attribute vec3 position;
		attribute vec2 uv;
		varying vec2 vUv;
		void main() {
			vUv = uv;
			gl_Position = vec4(position, 1.0);
		}`,
    fragmentShader: `
		#ifdef GL_ES
			precision highp float;
		#endif
		uniform sampler2D tDiffuse;
		uniform vec3 color;
		uniform float width;
		uniform float opacity;
		uniform vec2 resolution;
		uniform vec2 offset[24];
		varying vec2 vUv;
		vec2 piexl = resolution * width; // 描边厚度

		vec4 stroke(){
			vec4 blurColor = vec4(0.0);
			vec4 Color = vec4(0.0);
			for (int i = 0; i < 24; i++ ) {
				Color = texture2D(tDiffuse, vUv + (offset[i] * piexl));
				if (Color.a > 0.0) {
					blurColor += Color;
				}
			}
			if (blurColor.a < 0.001) {
				return vec4(0.0);
			} else {
				return vec4(color, opacity);
			}
		}
		void main() {
			vec4 Color = texture2D(tDiffuse, vUv);
			if (Color.a > 0.0 || width == 0.0) {
				gl_FragColor = Color;
			} else {
				gl_FragColor = stroke();
			}
		}`,
    blending: NoBlending, // 渲染时不与背景融合计算 直接替换
    depthTest: false,
    depthWrite: false,
    transparent: true
});

// 过滤着色器
const filterSelectShader = new RawShaderMaterial({
    uniforms: {
        selectTexture: { value: null },
        blurTexture: { value: null }
    },
    vertexShader: `
		attribute vec3 position;
		attribute vec2 uv;
		varying vec2 vUv;
		void main() {
			vUv = uv;
			gl_Position = vec4(position, 1.0);
		}
	`,
    fragmentShader: `
		#ifdef GL_ES
			precision highp float;
		#endif
		uniform sampler2D selectTexture;
		uniform sampler2D blurTexture;
		varying vec2 vUv;

		void main() {
			gl_FragColor = texture2D(blurTexture, vUv);
			gl_FragColor.a -= (texture2D(selectTexture, vUv).a);
		}
	`,
    blending: NoBlending, // 渲染时不与背景融合计算 直接替换
    depthTest: false,
    depthWrite: false,
    transparent: true
});

const copyShader = new ShaderMaterial({
    uniforms: {
        map: { value: null }, // 贴图纹理
    },
    vertexShader: `
        ${ShaderChunk.common}
        ${ShaderChunk.logdepthbuf_pars_vertex}
        varying vec2 vUv;
        void main() {
            vUv = uv;
            // gl_Position = vec4(position, 1.0);
            // gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);
            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            ${ShaderChunk.logdepthbuf_vertex}
        }
    `,
    fragmentShader: `
        ${ShaderChunk.logdepthbuf_pars_fragment}
        uniform sampler2D map;
        varying vec2 vUv;
        void main() {
            gl_FragColor = texture2D(map, vUv);
            ${ShaderChunk.logdepthbuf_fragment}
        }
    `,
    depthTest: true,
    depthWrite: true,
    transparent: true
});

// 场景对象
const scene = new Scene();

// 对象描边
export default function stroke_pass(renderer: WebGLRenderer, camera: Camera, object3d: Object3D, baseDepthTexture: DepthTexture,devicePixelRatio:number) {

    const { stroke_enable = false, stroke_color = "#04fa12", stroke_width = 1 } = object3d.userData;
    if (!stroke_enable || stroke_width === 0) {
        return;
    }

    // 保存原帧缓冲区
    const originRenderTarget = renderer.getRenderTarget();

    // 设置选中目标的帧缓冲区
    renderer.getSize(size);
    size.multiplyScalar(devicePixelRatio);
    selectTextureTarget.setSize(size.x, size.y);
    selectDethpTestTextureTarget.setSize(size.x, size.y);
    blur1TextureTarget.setSize(size.x, size.y);
    blur2TextureTarget.setSize(size.x, size.y);
    renderer.setRenderTarget(selectTextureTarget);
    scene.children = [object3d];
    renderer.clear();
    renderer.render(scene, camera);

    // 设置描边着色器的分辨率
    strokeShader.uniforms.resolution.value.set(1 / size.x, 1 / size.y);
    // 设置抗锯齿着色器的分辨率
    FXAAShaderMaterial.uniforms.resolution.value.set(1 / size.x, 1 / size.y);
    //描边颜色
    strokeShader.uniforms.color.value.set(stroke_color);
    strokeShader.uniforms.width.value = 1;


    //深度测试
    renderer.setRenderTarget(selectDethpTestTextureTarget);
    renderer.clear();
    depthTestShader.uniforms.map.value = selectTextureTarget.texture;
    depthTestShader.uniforms.baseDepth.value = baseDepthTexture;
    depthTestShader.uniforms.selectDepth.value = selectDepthTexture;
    fsQuad.render(renderer, depthTestShader);

    //一次描边
    renderer.setRenderTarget(blur1TextureTarget);
    strokeShader.uniforms.tDiffuse.value = selectDethpTestTextureTarget.texture;
    fsQuad.render(renderer, strokeShader);

    //二次描边
    renderer.setRenderTarget(blur2TextureTarget);
    strokeShader.uniforms.tDiffuse.value = blur1TextureTarget.texture;
    fsQuad.render(renderer, strokeShader);


    for (let i = 0; i < stroke_width - 2; i++) {
        //一次描边
        renderer.setRenderTarget(blur1TextureTarget);
        strokeShader.uniforms.tDiffuse.value = blur2TextureTarget.texture;
        fsQuad.render(renderer, strokeShader);

        //二次描边
        renderer.setRenderTarget(blur2TextureTarget);
        strokeShader.uniforms.tDiffuse.value = blur1TextureTarget.texture;
        fsQuad.render(renderer, strokeShader);
    }

    renderer.setRenderTarget(blur1TextureTarget);
    copyShader.uniforms.map.value = blur2TextureTarget.texture;
    fsQuad.render(renderer, copyShader);

    // 还原帧缓冲区 减去原始纹理保留描边
    renderer.setRenderTarget(originRenderTarget);
    filterSelectShader.uniforms.selectTexture.value = selectDethpTestTextureTarget.texture;
    filterSelectShader.uniforms.blurTexture.value = blur1TextureTarget.texture;
    filterSelectShader.blending = NormalBlending; // 与有的纹理融合计算 保留前面通道传递下来的纹理
    fsQuad.render(renderer, filterSelectShader);


    // 调试代码
    // renderer.setRenderTarget(null);
    // renderer.clearDepth();
    // copyShader.uniforms.tDiffuse.value = blur2TextureTarget.texture;
    // fsQuad.render(renderer, copyShader);
    // return;
}